package cz.cuni.mff.ufal.httpserver.protocol;

import java.io.Closeable;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

/**
 * 
 * @author Bartłomiej Etenkowski
 */
public class HttpResponse implements Closeable {
  public HttpResponse(OutputStream out) {
    this.out = out;
    this.cout = new PrintWriter(this.out, true);
  }
  
  private Status status = Status.OK;

  public void setStatus(Status status) {
    this.status = status;
  }
  
  public Status getStatus() {
    return status;
  }
  
  private HttpProtocol protocol = HttpProtocol.HTTP_10;

  public void setProtocol(HttpProtocol protocol) {
    this.protocol = protocol;
  }
  
  public HttpProtocol getProtocol() {
    return protocol;
  }
  
  public void putHeader(String name, String value) {
    headers.put(name, value);
  }
  
  public void putHeader(StandardHeader header, String value) {
    putHeader(header.getName(), value);
  }
  
  private final Map<String, String> headers = new HashMap<String, String>();
  
  public Map<String, String> getHeaders() {
    return headers;
  }
  
  private final OutputStream out;
  
  public OutputStream getOutputStream() {
    return out;
  }
  
  private final PrintWriter cout;
  
  public PrintWriter getPrintWriter() {
    return cout;
  }
  
  private boolean headersWritten = false;
    
  public void writeHeaders() {
    if (headersWritten); // TODO throw exception
    cout.print(String.format("%s %d %s\r\n", protocol.toString(), status.getCode(), status.toString()));
    for (Entry<String, String> header : headers.entrySet())
      cout.print(String.format("%s: %s\r\n", header.getKey(), header.getValue()));
    cout.print("\r\n");
    cout.flush();
    headersWritten = true;
  }
  
  private boolean closed = false;
  
  /**
   * Closes the underlying stream. The method does not have to be called manually as it is called by
   * the server. Closing of previously closed object will have no effect.
   */
  @Override
  public void close() throws java.io.IOException {
    if (closed) {
      return;
    }
    cout.close();
    out.close();
    closed = true;
  }
  

  /** Http response status. */
  public static enum Status {
    // informational
    CONTINUE(100, "Continue"),
    SWITCHING_PROTOCOLS(101, "Switching Protocols"),
    CONNECTION_TIMED_OUT(110, "Connection Timed Out"),
    CONNECTION_REFUSED(111, "Connection Refused"),
    
    // success
    OK(200, "OK"),
    CREATED(201, "Created"),
    ACCEPTED(202, "Accepted"),
    NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
    NO_CONTENT(204, "No Content"),
    RESET_CONTENT(205, "Reset Content"),
    PARTIAL_CONTENT(206, "Partial Content"),
    
    // redirection
    MULTIPLE_CHOICES(300, "Multiple Choices"),
    MOVED_PERMANENTLY(301, "Moved Permanently"),
    FOUND(302, "Found"),
    SEE_OTHER(303, "See Other"),
    NOT_MODIFIED(304, "Not Modified"),
    USE_PROXY(305, "Use Proxy"),
    /** Unused but reserved for older versions of the protocol. */
    ITEM_306(306, "306"),
    TEMPORARY_REDIRECT(307, "Temporary Redirect"),
    
    // error
    BAD_REQUEST(400, "Bad Request"),
    UNAUTHORIZED(401, "Unauthorized"),
    PAYMENT_REQUIRED(402, "Payment Required"),
    FORBIDDEN(403, "Forbidden"),
    NOT_FOUND(404, "Not Found"),
    METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
    NOT_ACCEPTABLE(406, "Not Acceptable"),
    PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
    REQUEST_TIMEOUT(408, "Request Timeout"),
    CONFLICT(409, "Conflict"),
    GONE(410, "Gone"),
    LENGTH_REQUIRED(411, "Length Required"),
    PRECONDITION_FAILED(412, "Precondition Failed"),
    REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
    REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
    UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
    REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
    EXPECTATION_FAILED(417, "Expectation Failed"),
    
    // internal error
    INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
    NOT_IMPLEMENTED(501, "Not Implemented"),
    BAD_GATEWAY(502, "Bad Gateway"),
    SERVICE_UNAVAILABLE(503, "Service Unavailable"),
    GATEWAY_TIMEOUT(504, "Gateway Timeout"),
    HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported");
    
    private int code;
    private String str;
    
    private Status(int code, String str) {
      this.code = code;
      this.str = str;
    }
    
    public int getCode() {
      return code;
    }
    
    @Override
    public String toString() {
      return str;
    }    
  }
  
  /** Standard headers used in HTTP response. */
  public static enum StandardHeader {
    CONTENT_TYPE("Content-Type"),
    CONTENT_LENGTH("Content-Length");
    
    private final String name;
    
    public String getName() {
      return name;
    }
    
    private StandardHeader(String name) {
      this.name = name;
    }
  }
}
